/**
 * Copyright (C) 2011 Andrew C. Love (DNC) <dnc.app.sup@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dnc.cloak.framework.pool;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.dnc.cloak.framework.spring.SpringIntegration;
import com.dnc.cloak.framework.util.ClassUtil;
import com.dnc.cloak.framework.util.dataconvert.DataConversionException;
import com.dnc.cloak.framework.util.dataconvert.DataConverter;
import com.dnc.cloak.service.ConfigurationService;

public class Pool<PoolType> {
	
	private static final Logger logger = Logger.getLogger(Pool.class.getName());
    private int poolSize;
    private int freeCount = 0;
    private List<PoolType> objectPool = null;
    private String poolName = null;

    /**
     * Sets up a pool of a default size of <code>IPooledObjects</code>.  Actual
     * creation of the <code>IPooledObjects</code> is deferred until an object
     * is requested.
     * @param className
     *   the kind of object to be stored. <code>className</code> must implement
     *   <code>IPooledObject</code>
     */
    public Pool(String poolName) throws PoolException{
    	
    	int poolSize = PoolConstants.DEFAULT_POOL_SIZE;
    	
        try {
			ConfigurationService configurationService = (ConfigurationService)SpringIntegration.context.getBean("configurationService");
			poolSize = DataConverter.getInteger(configurationService.getProperty(PoolConstants.DEFAULT_POOL_SIZE_PROPERTY,PoolConstants.DEFAULT_POOL_SIZE_STRING));
		} 
        catch (DataConversionException ex) {
        	throw new PoolException(ex);
		}        
        
        initialize(poolName, poolSize);
        
    }
    
    /**
     * Sets up a pool of a specified size of <code>IPooledObjects</code>. Actual
     * creation of the <code>IPooledObjects</code> is deferred until an object
     * is requested.
     * @param className
     *   the kind of object to be stored. <code>className</code> must implement
     *   <code>IPooledObject</code>
     * @param poolSize
     *   the pool size.  PoolTypehis is the size to which the pool will acquiesce. 
     *   A pool may temporarily grow larger than than this size to accomodate demand.
     */
    public Pool(String className, int poolSize) {
        initialize(className, poolSize);
    }
    
    public void initialize(String poolNameArg, int poolSizeArg) {
    	
        this.poolName = poolNameArg;
        this.poolSize = poolSizeArg;
        
        logger.debug("Pool for " + poolName + " created, size is: " + poolSize);
        objectPool = new ArrayList<PoolType>(poolSize);
        
    }
    
    public int getPoolSize() {
    	return poolSize;
    }
    
    public int getPoolCount() {
    	return objectPool.size();
    }
    
    protected synchronized boolean isInPool(PoolType object) {
    	return objectPool.contains(object);
    }
    
    
    /**
     * Returns either a pooled object, or a newly created object if there are
     * no pooled objects available.  Pool size imposes no barrier on whether an 
     * object is created.  At any given time there may be more objects outstanding
     * (either in use or in the pool), than indicated by the actual <code>pool size</code>.
     * Objects are selected from the pool in a LIFO manner.
     * @return 
     *   an <code>IPooledObject</code>, either dynamically created or selected from 
     *   the pool of existing objects.  PoolTypehe <code>IPooledObject</code> is initialized
     *   prior to being returned to the caller.
     */
    @SuppressWarnings("unchecked")
	public synchronized PoolType request(Object[] args) throws PoolException {
    	
        try {
            PoolType pooledObject;

            if (freeCount == 0) {                
            	pooledObject = (PoolType)ClassUtil.forName(poolName).newInstance();                	
                logger.debug("Allocated new " + poolName);
            }
            else {
                pooledObject = objectPool.get(--freeCount);
                objectPool.remove(pooledObject);
              	logger.debug("Reusing " + poolName + " element at position " + freeCount + " in pool");
            }
               
            return pooledObject;
        }
        catch (Exception ex) {
            throw new PoolException(ex);
        }
        
    }
    
    public PoolType request() throws PoolException {
        return request(null);
    }

  

	/**
     * Releases an object back into the pool.  If the pool is already full,
     * the object is not "pooled". Released objects are placed at the end of 
     * the list, where they are first available upon <code>request()</code>.
     * A check is made to determine whether the object to be released is of
     * the proper type (as specified at pool creation).
     * @param object
     *   the object to be returned to the free pool
     */
    public synchronized void release(PoolType object) throws PoolException {
    	
        if (object != null && freeCount < poolSize) {
            if (object.getClass().getName().equals(poolName)) {
                if (!isInPool(object)) {
                	logger.debug("Returned " + poolName + " element to position " + freeCount + " in pool");
                	freeCount++;
                    objectPool.add(object);
                }
                else {
                	logger.debug("Object " + poolName + "(" + object.hashCode() + ") already exists in the pool");
                }
            }
            else {
                throw new PoolException("Attempt to return a " + object.getClass().getName() + " object to a " + poolName + " pool.");
            }
        }
        else {
        	 logger.debug("Discarding unneeded element - pool full");
        }
        
    }
    
}